// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/formats/webm/webm_tracks_parser.h"

#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "media/base/media_util.h"
#include "media/base/timestamp_constants.h"
#include "media/formats/webm/webm_constants.h"
#include "media/formats/webm/webm_content_encodings.h"

namespace media {

static TextKind CodecIdToTextKind(const std::string& codec_id)
{
    if (codec_id == kWebMCodecSubtitles)
        return kTextSubtitles;

    if (codec_id == kWebMCodecCaptions)
        return kTextCaptions;

    if (codec_id == kWebMCodecDescriptions)
        return kTextDescriptions;

    if (codec_id == kWebMCodecMetadata)
        return kTextMetadata;

    return kTextNone;
}

static base::TimeDelta PrecisionCappedDefaultDuration(
    const double timecode_scale_in_us,
    const int64_t duration_in_ns)
{
    if (duration_in_ns <= 0)
        return kNoTimestamp;

    int64_t mult = duration_in_ns / 1000;
    mult /= timecode_scale_in_us;
    if (mult == 0)
        return kNoTimestamp;

    mult = static_cast<double>(mult) * timecode_scale_in_us;
    return base::TimeDelta::FromMicroseconds(mult);
}

WebMTracksParser::WebMTracksParser(const scoped_refptr<MediaLog>& media_log,
    bool ignore_text_tracks)
    : ignore_text_tracks_(ignore_text_tracks)
    , media_log_(media_log)
    , audio_client_(media_log)
    , video_client_(media_log)
{
    Reset();
}

WebMTracksParser::~WebMTracksParser() { }

void WebMTracksParser::Reset()
{
    ResetTrackEntry();
    reset_on_next_parse_ = false;
    audio_track_num_ = -1;
    audio_default_duration_ = -1;
    audio_decoder_config_ = AudioDecoderConfig();
    video_track_num_ = -1;
    video_default_duration_ = -1;
    video_decoder_config_ = VideoDecoderConfig();
    text_tracks_.clear();
    ignored_tracks_.clear();
    detected_audio_track_count_ = 0;
    detected_video_track_count_ = 0;
    detected_text_track_count_ = 0;
    media_tracks_.reset(new MediaTracks());
}

void WebMTracksParser::ResetTrackEntry()
{
    track_type_ = -1;
    track_num_ = -1;
    track_name_.clear();
    track_language_.clear();
    codec_id_ = "";
    codec_private_.clear();
    seek_preroll_ = -1;
    codec_delay_ = -1;
    default_duration_ = -1;
    audio_client_.Reset();
    video_client_.Reset();
}

int WebMTracksParser::Parse(const uint8_t* buf, int size)
{
    if (reset_on_next_parse_)
        Reset();

    reset_on_next_parse_ = true;

    WebMListParser parser(kWebMIdTracks, this);
    int result = parser.Parse(buf, size);

    if (result <= 0)
        return result;

    // For now we do all or nothing parsing.
    return parser.IsParsingComplete() ? result : 0;
}

base::TimeDelta WebMTracksParser::GetAudioDefaultDuration(
    const double timecode_scale_in_us) const
{
    return PrecisionCappedDefaultDuration(timecode_scale_in_us,
        audio_default_duration_);
}

base::TimeDelta WebMTracksParser::GetVideoDefaultDuration(
    const double timecode_scale_in_us) const
{
    return PrecisionCappedDefaultDuration(timecode_scale_in_us,
        video_default_duration_);
}

WebMParserClient* WebMTracksParser::OnListStart(int id)
{
    if (id == kWebMIdContentEncodings) {
        DCHECK(!track_content_encodings_client_.get());
        track_content_encodings_client_.reset(
            new WebMContentEncodingsClient(media_log_));
        return track_content_encodings_client_->OnListStart(id);
    }

    if (id == kWebMIdTrackEntry) {
        ResetTrackEntry();
        return this;
    }

    if (id == kWebMIdAudio)
        return &audio_client_;

    if (id == kWebMIdVideo)
        return &video_client_;

    return this;
}

bool WebMTracksParser::OnListEnd(int id)
{
    if (id == kWebMIdContentEncodings) {
        DCHECK(track_content_encodings_client_.get());
        return track_content_encodings_client_->OnListEnd(id);
    }

    if (id == kWebMIdTrackEntry) {
        if (track_type_ == -1 || track_num_ == -1) {
            MEDIA_LOG(ERROR, media_log_) << "Missing TrackEntry data for "
                                         << " TrackType " << track_type_
                                         << " TrackNum " << track_num_;
            return false;
        }

        if (track_type_ != kWebMTrackTypeAudio && track_type_ != kWebMTrackTypeVideo && track_type_ != kWebMTrackTypeSubtitlesOrCaptions && track_type_ != kWebMTrackTypeDescriptionsOrMetadata) {
            MEDIA_LOG(ERROR, media_log_) << "Unexpected TrackType " << track_type_;
            return false;
        }

        TextKind text_track_kind = kTextNone;
        if (track_type_ == kWebMTrackTypeSubtitlesOrCaptions) {
            text_track_kind = CodecIdToTextKind(codec_id_);
            if (text_track_kind == kTextNone) {
                MEDIA_LOG(ERROR, media_log_) << "Missing TrackEntry CodecID"
                                             << " TrackNum " << track_num_;
                return false;
            }

            if (text_track_kind != kTextSubtitles && text_track_kind != kTextCaptions) {
                MEDIA_LOG(ERROR, media_log_) << "Wrong TrackEntry CodecID"
                                             << " TrackNum " << track_num_;
                return false;
            }
        } else if (track_type_ == kWebMTrackTypeDescriptionsOrMetadata) {
            text_track_kind = CodecIdToTextKind(codec_id_);
            if (text_track_kind == kTextNone) {
                MEDIA_LOG(ERROR, media_log_) << "Missing TrackEntry CodecID"
                                             << " TrackNum " << track_num_;
                return false;
            }

            if (text_track_kind != kTextDescriptions && text_track_kind != kTextMetadata) {
                MEDIA_LOG(ERROR, media_log_) << "Wrong TrackEntry CodecID"
                                             << " TrackNum " << track_num_;
                return false;
            }
        }

        std::string encryption_key_id;
        if (track_content_encodings_client_) {
            DCHECK(!track_content_encodings_client_->content_encodings().empty());
            // If we have multiple ContentEncoding in one track. Always choose the
            // key id in the first ContentEncoding as the key id of the track.
            encryption_key_id = track_content_encodings_client_->content_encodings()[0]->encryption_key_id();
        }

        EncryptionScheme encryption_scheme = encryption_key_id.empty() ? Unencrypted() : AesCtrEncryptionScheme();

        if (track_type_ == kWebMTrackTypeAudio) {
            detected_audio_track_count_++;
            if (audio_track_num_ == -1) {
                audio_track_num_ = track_num_;
                audio_encryption_key_id_ = encryption_key_id;

                if (default_duration_ == 0) {
                    MEDIA_LOG(ERROR, media_log_) << "Illegal 0ns audio TrackEntry "
                                                    "DefaultDuration";
                    return false;
                }
                audio_default_duration_ = default_duration_;

                DCHECK(!audio_decoder_config_.IsValidConfig());
                if (!audio_client_.InitializeConfig(
                        codec_id_, codec_private_, seek_preroll_, codec_delay_,
                        encryption_scheme, &audio_decoder_config_)) {
                    return false;
                }
                media_tracks_->AddAudioTrack(
                    audio_decoder_config_,
                    static_cast<StreamParser::TrackId>(track_num_), "main", track_name_,
                    track_language_);
            } else {
                MEDIA_LOG(DEBUG, media_log_) << "Ignoring audio track " << track_num_;
                ignored_tracks_.insert(track_num_);
            }
        } else if (track_type_ == kWebMTrackTypeVideo) {
            detected_video_track_count_++;
            if (video_track_num_ == -1) {
                video_track_num_ = track_num_;
                video_encryption_key_id_ = encryption_key_id;

                if (default_duration_ == 0) {
                    MEDIA_LOG(ERROR, media_log_) << "Illegal 0ns video TrackEntry "
                                                    "DefaultDuration";
                    return false;
                }
                video_default_duration_ = default_duration_;

                DCHECK(!video_decoder_config_.IsValidConfig());
                if (!video_client_.InitializeConfig(codec_id_, codec_private_,
                        encryption_scheme,
                        &video_decoder_config_)) {
                    return false;
                }
                media_tracks_->AddVideoTrack(
                    video_decoder_config_,
                    static_cast<StreamParser::TrackId>(track_num_), "main", track_name_,
                    track_language_);
            } else {
                MEDIA_LOG(DEBUG, media_log_) << "Ignoring video track " << track_num_;
                ignored_tracks_.insert(track_num_);
            }
        } else if (track_type_ == kWebMTrackTypeSubtitlesOrCaptions || track_type_ == kWebMTrackTypeDescriptionsOrMetadata) {
            detected_text_track_count_++;
            if (ignore_text_tracks_) {
                MEDIA_LOG(DEBUG, media_log_) << "Ignoring text track " << track_num_;
                ignored_tracks_.insert(track_num_);
            } else {
                std::string track_num = base::Int64ToString(track_num_);
                text_tracks_[track_num_] = TextTrackConfig(
                    text_track_kind, track_name_, track_language_, track_num);
            }
        } else {
            MEDIA_LOG(ERROR, media_log_) << "Unexpected TrackType " << track_type_;
            return false;
        }

        track_type_ = -1;
        track_num_ = -1;
        default_duration_ = -1;
        track_name_.clear();
        track_language_.clear();
        codec_id_ = "";
        codec_private_.clear();
        track_content_encodings_client_.reset();

        audio_client_.Reset();
        video_client_.Reset();
        return true;
    }

    return true;
}

bool WebMTracksParser::OnUInt(int id, int64_t val)
{
    int64_t* dst = NULL;

    switch (id) {
    case kWebMIdTrackNumber:
        dst = &track_num_;
        break;
    case kWebMIdTrackType:
        dst = &track_type_;
        break;
    case kWebMIdSeekPreRoll:
        dst = &seek_preroll_;
        break;
    case kWebMIdCodecDelay:
        dst = &codec_delay_;
        break;
    case kWebMIdDefaultDuration:
        dst = &default_duration_;
        break;
    default:
        return true;
    }

    if (*dst != -1) {
        MEDIA_LOG(ERROR, media_log_) << "Multiple values for id " << std::hex << id
                                     << " specified";
        return false;
    }

    *dst = val;
    return true;
}

bool WebMTracksParser::OnFloat(int id, double val)
{
    return true;
}

bool WebMTracksParser::OnBinary(int id, const uint8_t* data, int size)
{
    if (id == kWebMIdCodecPrivate) {
        if (!codec_private_.empty()) {
            MEDIA_LOG(ERROR, media_log_)
                << "Multiple CodecPrivate fields in a track.";
            return false;
        }
        codec_private_.assign(data, data + size);
        return true;
    }
    return true;
}

bool WebMTracksParser::OnString(int id, const std::string& str)
{
    if (id == kWebMIdCodecID) {
        if (!codec_id_.empty()) {
            MEDIA_LOG(ERROR, media_log_) << "Multiple CodecID fields in a track";
            return false;
        }

        codec_id_ = str;
        return true;
    }

    if (id == kWebMIdName) {
        track_name_ = str;
        return true;
    }

    if (id == kWebMIdLanguage) {
        // Check that the language string is in ISO 639-2 format (3 letter code of a
        // language, all lower-case letters).
        if (str.size() != 3 || str[0] < 'a' || str[0] > 'z' || str[1] < 'a' || str[1] > 'z' || str[2] < 'a' || str[2] > 'z') {
            VLOG(2) << "Ignoring kWebMIdLanguage (not ISO 639-2 compliant): " << str;
            track_language_ = "und";
        } else {
            track_language_ = str;
        }
        return true;
    }

    return true;
}

} // namespace media
